﻿using System;
using System.Text;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Campus
{
    class SelectionBox
    {
        #region Fields and Properties

            #region Private

            private Player m_player;
            private Vector2? m_clickSpot;
            private MouseState m_oldState;
        
            static private Texture2D m_pixelTexture;

            #endregion

            #region Public

            /// <summary>
            /// The area, in screen coordinates that is deemed valid for selecting.
            /// </summary>
            public Rectangle SelectableArea { get; set; }

            /// <summary>
            /// The color of the selection box.
            /// </summary>
            public Color Color { get; set; }

            /// <summary>
            /// The thickness of the selection box.
            /// </summary>
            public int LineThickness { get; set; }

            #endregion

        #endregion

        #region Constructors

        public SelectionBox(Player owner, Level level, Rectangle selectableArea) : 
                       this(owner, selectableArea, Color.White, 2) {}

        public SelectionBox(Player owner, Rectangle selectableArea, Color color, int thickness)
        {
            this.SelectableArea = selectableArea;
            this.Color = color;
            this.LineThickness = thickness;

            this.m_player = owner;
            this.m_clickSpot = null;
            this.m_oldState = Mouse.GetState();
        }

        #endregion

        #region Instance Methods

        #region Public

            public void Update(Level currentLevel)
        {
            MouseState newState = Mouse.GetState();

            // If the mouse has been clicked
            if (m_oldState.LeftButton == ButtonState.Released &&
                newState.LeftButton == ButtonState.Pressed)
            {
                // If the mouse is in the 'selectable area'
                if (IsMouseInSelectableArea(SelectableArea))
                {
                    // Set the clicked spot as the origin for the selection box
                    m_clickSpot = new Vector2(newState.X, newState.Y);
                }
            }
            // If the left mouse button is not down
            else if (newState.LeftButton == ButtonState.Released)
            {
                if (m_clickSpot != null)
                {
                    if (IsMouseInSelectableArea(SelectableArea))
                    {
                        // The user wants to select some units or buildings
                        Vector2 newPos = new Vector2(newState.X, newState.Y);

                        // If the user has made a selection box small enough or not at all
                        // (just clicked in the same spot), only select one unit
                        if (Vector2.Distance(m_clickSpot.Value, newPos) <= 1.0f)
                        {
                            SelectSingleEntity(newPos, currentLevel);
                        }
                        // Otherwise, select all the units in the selection box, filter them,
                        // and set the Player's selected units
                        else
                        {
                            SelectEntities(newPos, currentLevel);
                        }
                    }

                    // The mouse has been released so 'destroy' the selection box
                    m_clickSpot = null;
                }
            }

            // Set the 'new' old state as the current state
            m_oldState = newState;
        }

            public void Draw(SpriteBatch spriteBatch)
        {
            // Only draw if the mouse has been clicked and is still within the selectable area
            if (m_clickSpot != null && IsMouseInSelectableArea(SelectableArea))
            {
                spriteBatch.Begin();

                int x1 = (int)m_clickSpot.Value.X;
                int y1 = (int)m_clickSpot.Value.Y;
                int x2 = (int)m_oldState.X;
                int y2 = (int)m_oldState.Y;

                // Draw the horizontal lines
                if (x1 < x2)
                {
                    // The mouse is to the right of where it started
                    spriteBatch.Draw(m_pixelTexture, new Rectangle(x1, y1, (x2 - x1), LineThickness), Color);

                    spriteBatch.Draw(m_pixelTexture, new Rectangle(x1, y2, (x2 - x1), LineThickness), Color);
                }
                else if (x1 > x2)
                {
                    // The mouse is to the left of where it started
                    spriteBatch.Draw(m_pixelTexture, new Rectangle(x2, y1, (x1 - x2), LineThickness), Color);

                    spriteBatch.Draw(m_pixelTexture, new Rectangle(x2, y2, (x1 - x2), LineThickness), Color);
                }

                // Draw the vertical lines
                if (y1 < y2)
                {
                    // The mouse is below where it started
                    spriteBatch.Draw(m_pixelTexture, new Rectangle(x1, y1, LineThickness, (y2 - y1)), Color);

                    spriteBatch.Draw(m_pixelTexture, new Rectangle(x2, y1, LineThickness, (y2 - y1)), Color);
                }
                else if (y1 > y2)
                {
                    // The mouse above where it started
                    spriteBatch.Draw(m_pixelTexture, new Rectangle(x1, y2, LineThickness, (y1 - y2)), Color);

                    spriteBatch.Draw(m_pixelTexture, new Rectangle(x2, y2, LineThickness, (y1 - y2)), Color);
                }

                spriteBatch.End();
            }
        }

        #endregion

        #region Private

            private void SelectSingleEntity(Vector2 clickSpot, Level currentLevel)
            {
                // Create a new list of selected entities
                List<BaseEntity> newSelectedList = new List<BaseEntity>();

                // Deselect the old selected entities
                foreach (BaseEntity entity in m_player.Selected)
                {
                    entity.IsSelected = false;
                }

                // Deselect the current unit for the Player
                m_player.CurrentUnit = null;

                // Create the point
                Point clickPoint = new Point((int)(clickSpot.X + m_player.ScreenPosition.X), 
                                             (int)(clickSpot.Y + m_player.ScreenPosition.Y));

                // Select the new units if they're usable
                foreach (BaseEntity entity in currentLevel.BaseEntities)
                {
                    if (entity.IsUsable && EntityContainsPoint(clickPoint, entity))
                    {
                        newSelectedList.Add(entity);
                    }
                }

                newSelectedList = FilterEntities(newSelectedList);

                // If there is more than one unit (will only happen with stacked
                // friendly units), just select the one at the front of the list
                if (newSelectedList.Count > 1)
                {
                    newSelectedList = new List<BaseEntity>() { newSelectedList[0] };
                }

                if (newSelectedList.Count > 0)
                {
                    m_player.CurrentUnit = newSelectedList[0];
                    m_player.CurrentUnit.IsSelected = true;
                }

                m_player.Selected = newSelectedList;
            }

            private void SelectEntities(Vector2 endPosition, Level currentLevel)
            {
                // Create a new list of selected entities
                List<BaseEntity> newSelectedList = new List<BaseEntity>();

                // Deselect the old selected entities
                foreach (BaseEntity entity in m_player.Selected)
                {
                    entity.IsSelected = false;
                }

                // Deselect the current unit for the Player
                m_player.CurrentUnit = null;

                int x1 = (int)m_clickSpot.Value.X;
                int y1 = (int)m_clickSpot.Value.Y;
                int x2 = (int)endPosition.X;
                int y2 = (int)endPosition.Y;

                // Don't select anything if the 'box' is actually a line
                if (x1 == x2 || y1 == y2)
                {
                    m_player.Selected = newSelectedList;
                    return;
                }

                Rectangle selectBox;

                if (x1 < x2)
                {
                    if (y1 < y2)
                    {
                        selectBox = new Rectangle((int)m_player.ScreenPosition.X + x1, 
                                                  (int)m_player.ScreenPosition.Y + y1, 
                                                  (x2 - x1), (y2 - y1));
                    }
                    else
                    {
                        selectBox = new Rectangle((int)m_player.ScreenPosition.X + x1,
                                                  (int)m_player.ScreenPosition.Y + y2, 
                                                  (x2 - x1), (y1 - y2));
                    }
                }
                else
                {
                    if (y1 < y2)
                    {
                        selectBox = new Rectangle((int)m_player.ScreenPosition.X + x2,
                                                  (int)m_player.ScreenPosition.Y + y1, 
                                                  (x1 - x2), (y2 - y1));
                    }
                    else
                    {
                        selectBox = new Rectangle((int)m_player.ScreenPosition.X + x2,
                                                  (int)m_player.ScreenPosition.Y + y2, 
                                                  (x1 - x2), (y1 - y2));
                    }
                }

                // Select the new units
                foreach (BaseEntity entity in currentLevel.BaseEntities)
                {
                    if (entity.IsUsable && SelectionBoxContainsEntity(selectBox, entity))
                    {
                        newSelectedList.Add(entity);
                    }
                }

                // Filter the list of selected entities
                newSelectedList = FilterEntities(newSelectedList);
                
                // Set all the selected units as 'selected'
                foreach (BaseEntity entity in newSelectedList)
                {
                    entity.IsSelected = true;
                }

                // Set the unit at the front of the list to the 'selected unit'
                if (newSelectedList.Count > 0)
                {
                    m_player.CurrentUnit = newSelectedList[0];
                }

                m_player.Selected = newSelectedList;
            }

            private bool SelectionBoxContainsEntity(Rectangle selectBox, BaseEntity entity)
            {
                Rectangle entityRect = new Rectangle();

                if (entity is Structure)
                {
                    entityRect = new Rectangle((int)(entity.BoundingSphere.Center.X - entity.BoundingSphere.Radius),
                                               (int)(entity.BoundingSphere.Center.Y - entity.BoundingSphere.Radius),
                                               (int)(entity.BoundingSphere.Radius * 2),
                                               (int)(entity.BoundingSphere.Radius * 2));
                }
                else
                {
                    entityRect = new Rectangle((int)(entity.Position.X - entity.Origin.X),
                                               (int)(entity.Position.Y - entity.Origin.Y),
                                               (int)entity.Size.X, (int)entity.Size.Y);
                }

                return (selectBox.Contains(entityRect) || selectBox.Intersects(entityRect));
            }

            private bool EntityContainsPoint(Point clickPoint, BaseEntity entity)
            {
                Rectangle entityRect = new Rectangle();

                if (entity is Structure)
                {
                    entityRect = new Rectangle((int)(entity.BoundingSphere.Center.X - entity.BoundingSphere.Radius),
                                               (int)(entity.BoundingSphere.Center.Y - entity.BoundingSphere.Radius),
                                               (int)(entity.BoundingSphere.Radius * 2),
                                               (int)(entity.BoundingSphere.Radius * 2));
                }
                else
                {
                    entityRect = new Rectangle((int)(entity.Position.X - entity.Origin.X),
                                               (int)(entity.Position.Y - entity.Origin.Y),
                                               (int)entity.Size.X, (int)entity.Size.Y);
                }

                return (entityRect.Contains(clickPoint));
            }

            private List<BaseEntity> FilterEntities(List<BaseEntity> entityList)
            {
                List<BaseEntity> filterList = new List<BaseEntity>();

                int numEnemyUnits = 0;
                int numFriendlyUnits = 0;
                int numBuildings = 0;

                // Determines what has been selected and how many
                foreach (BaseEntity entity in entityList)
                {
                    if (entity is Structure)
                    {
                        numBuildings++;
                    }
                    else if (entity is Unit)
                    {
                        if (entity.TeamNumber == m_player.TeamNumber)
                        {
                            numFriendlyUnits++;
                        }
                        else
                        {
                            numEnemyUnits++;
                        }
                    }
                    // THIS IS NEEDED BECAUSE THE 'ENEMY UNITS' ARE NOT UNITS!
                    else
                    {
                        numEnemyUnits++;
                    }
                }

                // The filtering criteria
                if (numFriendlyUnits > 0)
                {
                    // Select ALL the friendly units no matter what else is selected
                    foreach (BaseEntity entity in entityList)
                    {
                        if (entity is Unit && (entity.TeamNumber == m_player.TeamNumber))
                        {
                            filterList.Add(entity);
                        }
                    }
                }
                else if (numFriendlyUnits == 0 && numBuildings > 0 && numEnemyUnits != 1)
                {
                    // Only select the building if there is only one selected and the person
                    // isn't trying to select an enemy unit
                    foreach (BaseEntity entity in entityList)
                    {
                        if (entity is Structure)
                        {
                            filterList.Add(entity);
                            // There's no reason to contrain to only selected one structure unless we're intelligently pulling the most likely one out of the list, which we aren't.
                            //break;
                        }
                    }
                }            
                else if (numEnemyUnits == 1)
                {
                    // Only select the enemy unit if it is the only one selected
                    foreach (BaseEntity entity in entityList)
                    {
                        if (entity is Unit && (entity.TeamNumber != m_player.TeamNumber))
                        {
                            filterList.Add(entity);
                            break;
                        }
                        // THIS IS NEEDED BECAUSE THE ENEMY UNITS ARE NOT UNITS!
                        else if (entity.TeamNumber != m_player.TeamNumber)
                        {
                            filterList.Add(entity);
                            break;
                        }
                    }
                }

                return filterList;
            }

        #endregion

        #endregion

        #region Static Methods

        static public bool IsMouseInSelectableArea(Rectangle viewArea)
        {
            MouseState state = Mouse.GetState();

            float x = state.X;
            float y = state.Y;

            if (x >= viewArea.X &&
                y >= viewArea.Y &&
                x <= viewArea.X + viewArea.Width &&
                y <= viewArea.Y + viewArea.Height)
            {
                return true;
            }

            return false;
        }

        static public void InitializeSelectionBoxTexture(ContentManager Content)
        {
            SelectionBox.m_pixelTexture = Content.Load<Texture2D>("SelectionBox/pixel");
        }

        #endregion
    }
}
